; pm_system.asm - system entry and basic architecture initialization
; kernel.ld references pm_system.o:start as the system entry point,
; which is were the bootloader jumps to after loading the kernel image
;
;  Copyright 2006 Corey Tabaka
;
;  Licensed under the Apache License, Version 2.0 (the "License");
;  you may not use this file except in compliance with the License.
;  You may obtain a copy of the License at
;
;      http://www.apache.org/licenses/LICENSE-2.0
;
;  Unless required by applicable law or agreed to in writing, software
;  distributed under the License is distributed on an "AS IS" BASIS,
;  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;  See the License for the specific language governing permissions and
;  limitations under the License.
;

section .text

[extern _main]
[extern _interrupt_handler]
[extern _kstack]

[extern start_ctors]
[extern end_ctors]
[extern start_dtors]
[extern end_dtors]

[extern bss]
[extern bss_end]

[extern _multiBootInfo]

[global start]
[global __exit]
[global _idt]
[global _idt_end]
[global _idtr]
[global _gdt]
[global _gdtr]
[global _tss_gde]

; macros for interrupt stubs and idt entries
%define NUM_INT 0x31
%define NUM_EXC 0x14

%define INT(x) _isr %+ x
%define IDT(x) _idt %+ x

; The magic number for the Multiboot header.
%define MULTIBOOT_HEADER_MAGIC 0x1BADB002

; The flags for the Multiboot header.
%ifdef __ELF__
%define MULTIBOOT_HEADER_FLAGS 0x00000002
%else
%define MULTIBOOT_HEADER_FLAGS 0x00010002
%endif

; The magic number passed by a Multiboot-compliant boot loader.
%define MULTIBOOT_BOOTLOADER_MAGIC 0x2BADB002


bits 32
start:
	jmp real_start
     
     ; align 32 bits boundary.
     align 4

     ; multiboot header.
multiboot_header:
     ; magic
     dd MULTIBOOT_HEADER_MAGIC
     ; flags
     dd MULTIBOOT_HEADER_FLAGS
     ; checksum
     dd -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS)
%ifndef __ELF__
     ; header_addr
     dd multiboot_header
     ; load_addr
     dd start
     ; load_end_addr
     dd bss
     ; bss_end_addr
     dd bss_end				; don't handle the bss, the kernel will take care of it
     ; entry_addr
     dd real_start
%endif

real_start:
	; save the multiboot info address if loaded by a multiboot loader
	cmp eax,MULTIBOOT_BOOTLOADER_MAGIC
	jne .nombinfo
	mov [_multiBootInfo],ebx
.nombinfo:

	; setup interrupt service routine stubs
	mov esi,INT(0)
	mov edi,IDT(0)
	
	mov ecx,NUM_INT

.loop:
	mov ebx,esi
	mov [edi],bx				; low word in IDT(n).low
	shr ebx,16
	mov [edi + 6],bx			; high word in IDT(n).high
	
	add esi,INT(1) - INT(0)		; index the next ISR stub
	add edi,8					; index the next IDT entry
	
	loop .loop
	
	lidt [_idtr]
	xor eax,eax
	mov cr3,eax
	
	; setup the gdt
	lgdt [_gdtr]
		
	mov ax,datasel
	mov ds,ax
	mov es,ax
	mov fs,ax
	mov ss,ax
	mov gs,ax
	
	mov esp,_kstack
	
	; C environment initialization
	
	; zero the bss section
	mov edi,bss				; starting address of the bss
	mov ecx,bss_end			; find the length of the bss in bytes
	sub ecx,edi
	shr ecx,2				; convert to dwords, since the bss is page aligned anyway
.loop2:
	mov dword [edi],0
	add edi,4
	loop .loop2
	
	; call constructors
	call do_ctors
	
	; call the primary module
	call _main

__exit:
	
	; call destructors
	;call do_dtors

.stop:						; just sit around waiting for interrupts
	hlt						; interrupts will unhalt the processor
	pause
	jmp .stop				; so jump back to halt to conserve power

; call global constructors
do_ctors:
	mov edi,start_ctors		; load start of the constructor list
	cmp edi,end_ctors		; see if the list is empty
	je .done
.loop:
	call dword [edi]
	add edi,4				; move to the next entry
	cmp edi,end_ctors
	jne .loop
.done:
	ret

; call global destructors
do_dtors:
	mov edi,start_dtors		; load start of the destructor list
	cmp edi,end_dtors		; see if the list is empty
	je .done
.loop:
	call dword [edi]
	add edi,4				; move to the next entry
	cmp edi,end_dtors
	jne .loop
.done:
	ret

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ISRs
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; I shouldn't have to do this!
%macro PUSHB 1
	db 6Ah
	db %1
%endmacro

%assign i 0

%rep NUM_EXC

; only some exceptions push error codes
%if i == 8 || (i >= 10 && i <= 14) || i == 17 
INT(i):
	nop					; error code already pushed
	nop					; nop+nop=same length as push byte
	PUSHB i				; exception number
	jmp int_stub
%else
INT(i):
	push byte 0			; fake error code
	PUSHB i				; exception number
	jmp int_stub
%endif

%assign i i+1
%endrep

%rep (NUM_INT - NUM_EXC)

INT(i):
	push byte 0			; fake error code
	PUSHB i				; exception number
	jmp int_stub

%assign i i+1
%endrep

int_stub:
	push gs			; push segment registers
	push fs			; 
	push es			; 
	push ds			; 
	pusha			; push GP registers
	mov ax,datasel	; put known-good values...
	mov ds,eax		; ...in segment registers
	mov es,eax		; 
	mov fs,eax		; 
	mov gs,eax		; 
	mov eax,esp		; store task_esp (push esp is not consistent across models)
	push eax		
	mov eax,esp		; push pointer to regs_t
	push eax
	
	;mov eax,_interrupt_handler
	;call eax
	call _interrupt_handler
	
	pop eax			; drop pointer to regs_t
	pop eax			; restore task_esp
	mov esp,eax
	popa			; pop GP registers
	pop ds			; pop segment registers
	pop es
	pop fs
	pop gs
	add esp,8		; drop exception number and error code
	iret

; hex print routine put value in edx, put starting screen position in di
print_hex:
	push gs
	push cx
	push ax
	push bx
	push edx
	
	mov ax,videosel		; setup video selector
	mov gs,ax
	
	and di,0xfffe		; make sure screen offset is on a two byte boundry
	
	mov cx,8			; print out 8 chars
.loop:
	rol edx,4			; rotate upper byte around to lowest nibble
	mov ax, 0x070f		; ah = attribute, al = digit mask
	and al, dl			; mask digit into al
	add al, 0x90		; convert al to ascii hex with next four instructions
	daa
	adc al, 0x40
	daa
	mov word [gs:di], ax	; write char
	add di, 2			; advance index/column
	loop .loop
	
	pop edx
	pop bx
	pop ax
	pop cx
	pop gs
	ret

section .data
bits 32

; gdt for use with kernel module and interrupts
_gdtr:
	dw _gdt_end - _gdt -1	; descriptor limit
	dd _gdt
	;dw 0

; global descriptor table
; first entry null
_gdt:
	dd 0
	dd 0

; ring 0 descriptors	
codesel equ $-_gdt
_code_gde:
	dw 0xffff			; limit 15..0
	dw 0x0000			; base 23..0
	db 0x00
	db 10011010b		; P(1) DPL(00) S(1) 1 C(0) R(1) A(0)
	db 11001111b		; G(1) D(1) 0 0 Limit[19..16]
	db 0x0				; base 31..24
	
datasel equ $-_gdt
_data_gde:
	dw 0xffff
	dw 0x0000
	db 0x00
	db 10010010b		; P(1) DPL(00) S(1) 0 E(0) W(1) A(0)
	db 11001111b		; G(1) B(1) 0 0 Limit[19..16]
	db 0x0
	
videosel equ $-_gdt
_video_gde:
	dw 0xffff
	dw 0x8000
	db 0x0b
	db 10010010b		; P(1) DPL(00) S(1) 0 E(0) W(1) A(0)
	db 11001111b		; G(1) B(1) 0 0 Limit[19..16]
	db 0x0
	
%if 1
; ring 3 descriptors	
user_codesel equ $-_gdt
_user_code_gde:
	dw 0xffff			; limit 15..0
	dw 0x0000			; base 23..0
	db 0x00
	db 11111010b		; P(1) DPL(11) S(1) 1 C(0) R(1) A(0)
	db 11001111b		; G(1) D(1) 0 0 Limit[19..16]
	db 0x0				; base 31..24
	
user_datasel equ $-_gdt
_user_data_gde:
	dw 0xffff
	dw 0x0000
	db 0x00
	db 11110010b		; P(1) DPL(11) S(1) 0 E(0) W(1) A(0)
	db 11001111b		; G(1) B(1) 0 0 Limit[19..16]
	db 0x0
%endif

; TSS descriptor
%if 0
_tss_gde:
	dw 0				; limit 15:00
	dw 0				; base 15:00
	db 0				; base 23:16
	db 0xe9				; P(1) DPL(11) 0 10 B(0) 1
	db 0x00				; G(0) 0 0 AVL(0) Limit[19..16]
	dw 0				; base 31:24
%endif

_gdt_end:

_idtr:
	dw _idt_end - _idt - 1	; IDT limit
	dd _idt

; interrupt descriptor table (IDT)
_idt:
	%assign i 0
	%rep NUM_INT-1
IDT(i):
	dw 0			; low 16 bits of ISR offset (int_tab[i] & 0FFFFh)
	dw codesel		; selector
	db 0
	db 0x8e		;0x8e	; present,ring 0,32-bit interrupt gate
	dw 0			; high 16 bits of ISR offset (int_tab[i] / 65536)
	
	%assign i i+1
	%endrep

; syscall int (ring 3)
IDT(i):
	dw 0			; low 16 bits of ISR offset (int_tab[i] & 0FFFFh)
	dw codesel		; selector
	db 0
	db 0xee			; present,ring 3,32-bit int gate
	dw 0			; high 16 bits of ISR offset (int_tab[i] / 65536)
_idt_end:
